package com.wksc.framework.impexp.handler.imp;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;

import com.wksc.framework.impexp.bean.Header;
import com.wksc.framework.impexp.bean.ImpReturn;
import com.wksc.framework.impexp.constants.IEConstants;
import com.wksc.framework.impexp.constants.ImpErr;
import com.wksc.framework.impexp.constants.PostfixType;
import com.wksc.framework.impexp.handler.BaseHandler;
import com.wksc.framework.impexp.log.ImpExpLog;
import com.wksc.framework.impexp.reader.Reader;
import com.wksc.framework.impexp.reader.ReaderFactory;
import com.wksc.framework.impexp.template.ImpTemplate;
import com.wksc.framework.impexp.util.ClassFactory;
import jxl.read.biff.BiffException;
import jxl.write.WriteException;

/**
 * <p>
 * Title(文件名): ImpModeHandler.java
 * </p>
 * <p>
 * Description(描述): 导入模式处理类的基类
 * </p>
 * <p>
 * Copyright(版权): Copyright (c) 2011
 * </p>
 * <p>
 * Company(公司): 成都四方信息技术有限公司
 * </p>
 * <p>
 * CreateTime(生成日期)：2011-12-1 上午11:13:45
 * </p>
 * 
 * @author wanglei
 * @version impExp
 */
public abstract class BaseImpHandler extends BaseHandler {
	private static final long serialVersionUID = -195722166531103899L;
	/** 导入模板 */
	protected ImpTemplate impTemplate;
	/** 导入的文件 */
	protected File impFile;
	/** 从导入文件中读取的数据 */
	protected Map<Integer, String[]> readData;
	/** 经过剔重后的错误文件 */
	protected Map<Integer, String> incorrectInfo = new TreeMap<Integer, String>();
	/** 导入文件的关键列列表,此列表为剔重的关键 */
	private List<String> keyColsList = new LinkedList<String>();
	/** 读取文件的具体处理类 */
	protected Reader reader;
	/** 空白行数量 */
	protected int blankLineNum;
	/** 记录日志 */
	protected static final ImpExpLog log = ClassFactory
			.getLog(BaseImpHandler.class);

	public BaseImpHandler(ImpTemplate impTemplate) throws BiffException,
			IOException {
		super();
		this.impTemplate = impTemplate;
		initHandler();
	}

	/*
	 * ====================================== 供外部调用的方法 start
	 * ===========================================
	 */
	/**
	 * 获取循环处理的次数
	 */
	@Override
	public int getLoopTime() {
		int totalLineNum = getTotalLineNum(this.impFile) - 1;
		int loopTime = totalLineNum / IEConstants.IMP_ONCE_PRELINE;
		if (0 != totalLineNum % IEConstants.IMP_ONCE_PRELINE) {
			loopTime = loopTime + 1;
		}
		return loopTime;
	}

	/**
	 * 解析导入文件内容的核心方法,用于解析导入文件的实际内容. 在读取导入文件原始数据后,会进行基本剔重
	 * 
	 * @param loopCount
	 *            当前循环次数
	 * @return 包含解析导入解析结果的对象, 其中包含进行过剔重后的数据,错误数据信息
	 * @throws java.io.FileNotFoundException
	 */
	@Override
	public ImpReturn getDividedData(int loopCount) {
		// 解析导入文件的实际内容
		readData = resolveContent(loopCount);

		// 组成导入处理返回值对象
		return new ImpReturn(readData, incorrectInfo, blankLineNum);
	}

	/**
	 * 方法描述：导入前进行预验证 预验证内容包括: 1. 导入文件中是否有数据(除掉表头行) 2. 标题行是否符合待导入的模块
	 * 
	 * @creator wanglei
	 * @return
	 */
	public ImpErr preProcess() {
		// 验证导入文件的总条数
		int totalNum = getTotalLineNum();
		if (0 >= totalNum) {
			//return NO_DATA;
		}
		// 验证导入文件的标题行是否正确
		return verifyHeader();
	}

	/**
	 * 方法描述：导入处理完成后写错误文件
	 * 
	 * @creator wanglei
	 * @param incorrectInfo
	 * @throws java.io.IOException
	 * @throws BiffException
	 * @throws WriteException
	 */
	public abstract void writeErrInfo(Map<Integer, String> incorrectInfo)
			throws IOException, BiffException, WriteException;

	/**
	 * 读取导入文件数据总的行数
	 * 
	 * @return 导入文件数据总行数
	 */
	public int getTotalLineNum() {
		// 返回导入文件数据总数,减一是去掉标题行
		return getTotalLineNum(this.impFile) - 1;
	}

	/**
	 * 获取待处理的文件后缀名
	 * 
	 * @return
	 */
	public abstract PostfixType getPostfix();

	/**
	 * 方法描述：设置导入文件
	 * 
	 * @creator wanglei
	 * @param file
	 * @throws java.io.IOException
	 * @throws BiffException
	 */
	public void setImpFile(File file) throws BiffException, IOException {
		this.impFile = file;
		reader.setFile(file);
	}

	/*
	 * ==================================== 供外部调用的方法 end
	 * ===========================================
	 */

	/*
	 * ====================================== 供子类调用的方法 start
	 * ===========================================
	 */
	/**
	 * 获取导入文件的总行数
	 * 
	 * @param impFile
	 *            导入的文件
	 * @return 导入文件的总行数
	 */
	protected abstract int getTotalLineNum(File impFile);

	/**
	 * 获取导入文件的标题行内容
	 * 
	 * @param impFile
	 *            导入的文件
	 * @return 导入文件的标题行, 若为空表示为空文件
	 */
	protected abstract String[] getImpHeader(File impFile);

	/**
	 * 此方法用于导入时对读取到的原始数据剔重 将区分数据的关键列拼接起来形成一个唯一的字符串
	 * 
	 * @param inputData
	 *            原始每行的原始数据
	 * @param keyColsIndex
	 *            在原始数据中哪些列为可以区分的关键列
	 * @return 拼接后生成的唯一关键字符串, 注意: 返回字符串可能为null
	 */
	protected String[] getKeyColStr(String[] inputData,
			Map<Byte, List<Byte>> keyColsIndex) {
		if (null == keyColsIndex || keyColsIndex.isEmpty()
				|| 0 == inputData.length) {
			return null;
		}
		//存储分组后关键列的字符串信息
		String[] keyColStrItem = new String[keyColsIndex.size()];
		Iterator<Entry<Byte, List<Byte>>> iter = keyColsIndex.entrySet()
				.iterator();
		Entry<Byte, List<Byte>> entry = null;
		int i = 0;//索引下标记录
		while (iter.hasNext()) {
			entry = iter.next();
			StringBuilder sb = new StringBuilder();
			for (Byte col : entry.getValue()) {
				sb.append(inputData[col]);
			}
			//增加一个"_"和分组值是为了让关键列达到绝对的唯一,因为当我们分别比较两个关键列时
			//列1与列1相等算重复,列2与列2相等算重复,那么当两行记录列1与列2的记录相等时,这时
			//判断就会出现问题,所以根据我们的分组再加个标识
			sb.append("_");
			sb.append(entry.getKey());
			keyColStrItem[i] = sb.toString();
			i++;
		}
		return keyColStrItem;
	}

	/**
	 * 剔除从导入文件中读取到的重复数据 设计思路: 根据数据中可以区别数据重复性的关键列,将
	 * 这些关键列传入getKeyRowStr方法生成一个唯一的字符串作为判重标准 然后根据此关键字符串跟一个保存此关键字的List中数据进行对比
	 * 若此关键字在List中已包含,表示此行数据为重复数据,将其从原数据Map中剔除
	 * 若不存在与List中,表示该行数据不重复,则将其关键字符串添加至该List以供后面的 数据进行验证.
	 * 
	 * @param lineNum
	 *            行号
	 * @param rowData
	 *            从导入文件读取的数据
	 * @param keyColsIndex
	 *            关键列的索引值数组
	 * @return 剔重后的数据集合
	 */
	protected boolean clearRepeat(int lineNum, String[] rowData,
			Map<Byte, List<Byte>> keyColsIndex) {
		boolean isRepeat = true;

		// 根据关键列索引行生成该行数据的关键值
		String[] keyColStrItem = getKeyColStr(rowData, keyColsIndex);

		// 验证生成的keyColStrItem是否为空,若为空则为无效数据
		if (null == keyColStrItem || keyColStrItem.length == 0) {
			incorrectInfo.put(lineNum, ClassFactory.getI18n().getInvalidline());
			return isRepeat;
		}

		//循环分组的关键列组成的Key
		for (String keyColStr : keyColStrItem) {
			//任意分组与当前记录对应分组有重复
			if (keyColsList.contains(keyColStr)) {
				incorrectInfo.put(lineNum, ClassFactory.getI18n()
						.getRepeatdata());
				isRepeat = true;
				break;
			} else {
				isRepeat = false;
			}
		}
		//经过前面的比较当前记录分组与对应记录分组都不重复,将当前记录的分组
		//加入到下次的比较队列
		if (!isRepeat) {
			for (String keyColStr : keyColStrItem) {
				keyColsList.add(keyColStr);
			}
		}
		return isRepeat;
	}

	/**
	 * [此方法暂未使用]--20111116 将错误数据添加至保存错误信息的列表中
	 * 
	 * @param lineNum
	 *            该错误数据在原导入文件中的行号
	 * @param incorrectInfo
	 *            错误信息
	 */
	protected void addIncorrectInfo(Integer lineNum, String incorrectInfo) {
		this.incorrectInfo.put(lineNum, incorrectInfo);
	}

	/**
	 * 方法描述：判断String数组中是否全部元素均为空白或null
	 * 
	 * @param rowData
	 * @return true-该数组全部元素均为空白; false-该数组中至少有一个元素不为null或空字符串
	 */
	protected boolean isBlankLine(String[] rowData) {
		if (null == rowData) {
			return true;
		}

		boolean isAllBlank = true;
		String tmpStr = "";

		for (int i = 0; i < rowData.length; i++) {
			tmpStr = rowData[i];
			if (null == tmpStr || tmpStr.trim().equals("")) {
				continue;
			} else {
				isAllBlank = false;
				break;
			}
		}
		return isAllBlank;
	}

	/**
	 * 解析导入文件内容
	 * 
	 * @param loopCount
	 *            当前分页数
	 * @return
	 */
	protected Map<Integer, String[]> resolveContent(int loopCount) {
		// 临时保存数据的Map
		Map<Integer, String[]> tempData = new TreeMap<Integer, String[]>();
		// 单条数据所在行号
		int lineNum;
		// 从导入文件中读取的单行数据
		String[] rowData;
		// 分页开始基数
		int pageStartNum = IEConstants.IMP_ONCE_PRELINE * (loopCount - 1);
		// 获取此次导入模板中用于区分关键列的索引数组
		Map<Byte, List<Byte>> keyRowsIndex = impTemplate.getKeyRowsIndexMap();
		// 计算此次总共读取多少行,得到的总行数需要减去标题行
		int readNum;
		// 将空白行数量初始化为0
		this.blankLineNum = 0;

		if (0 == (reader.countTotalLine() - 1)
				/ (loopCount * IEConstants.IMP_ONCE_PRELINE)) {
			readNum = (reader.countTotalLine() - 1)
					% IEConstants.IMP_ONCE_PRELINE;
		} else {
			readNum = IEConstants.IMP_ONCE_PRELINE;
		}

		for (int i = 1; i <= readNum; i++) {
			lineNum = pageStartNum + i;
			// 根据行号读取该行数据
			rowData = reader.readByRow(lineNum);

			// 检测该行数据是否全为空白
			if (isBlankLine(rowData)) {
				this.blankLineNum++;
				// 若该行为空行则继续处理下条数据
				continue;
			}
			/*
			 * 进行基本剔重. 如果数据不为重复数据则放入Map 如果数据为重复数据则忽略此条数据
			 */
			if (!clearRepeat(lineNum, rowData, keyRowsIndex)) {
				tempData.put(lineNum, rowData);
			}
		}
		return tempData;
	}

	/*
	 * ====================================== 供子类调用的方法 end
	 * ===========================================
	 */

	/*
	 * ========================================= 私有方法 start
	 * ===============================================
	 */
	/**
	 * 在正式读取文件前先解析并验证导入文件标题行是否正确
	 * 
	 * @throws java.io.FileNotFoundException
	 * @return 验证结果
	 */
	private ImpErr verifyHeader() {
		String[] impHeader = getImpHeader(impFile);
		return verifyHeader(impHeader, impTemplate.getFullHeader());
	}

	/**
	 * 验证导入文件的标题行是否和完整表头相匹配
	 * 
	 * @param impHeader
	 *            导入文件的表头
	 * @param fullHeader
	 *            导入文件对应模块的完整表头
	 * @return 验证结果
	 */
	private ImpErr verifyHeader(String[] impHeader, Header[] fullHeader) {
		// 首先假设导入的表头是正确的
		ImpErr err = ImpErr.CORRECT;
		// 假如两者其中之一为空或二者长度不等,直接返回
		if (null == impHeader || null == fullHeader
				|| (impHeader.length != fullHeader.length)) {
			return ImpErr.HEADER_NOT_MATCH;
		}

		// 遍历导入文件表头与完整表头进行对比
		for (int i = 0; i < impHeader.length; i++) {
			if (i == 0) {
				String tmp = delQuestionMark(impHeader[0]);
				impHeader[0] = tmp;
			}
			if (!impHeader[i].trim().equalsIgnoreCase(
					fullHeader[i].getHeadLabel().trim())) {
				log.info("导入文件的表头:" + impHeader[i].trim() + ";导入文件对应模块的完整表头:"
						+ fullHeader[i].getHeadLabel().trim());
				err = ImpErr.HEADER_NOT_MATCH;
				break;
			}
		}

		return err;
	}

	/**
	 * 
	 * 方法描述：删除表头中以？为标示的信息
	 * 
	 * @creator 李星江
	 * @param str
	 * @return
	 */
	private String delQuestionMark(String str) {
		//		 bt = null;
		//		byte[] btStr = null;
		try {
			byte[] bt = str.getBytes("UTF-8");
			if (bt != null && bt.length > 3) {
				if (bt[0] == (-17) && bt[1] == (-69) && bt[2] == (-65)) {
					byte[] btStr = Arrays.copyOfRange(bt, 3, bt.length);
					return new String(btStr, "UTF-8");
				}
			}
		} catch (UnsupportedEncodingException e) {
			log.error("Can not resolve the encoding format!", e);
		}
		return str;
	}

	/**
	 * 初始化impModeHandler
	 * 
	 *            导入模板
	 * @throws java.io.FileNotFoundException
	 */
	private void initHandler() throws IOException, BiffException {
		reader = ReaderFactory.getReader(getPostfix());
	}
	/*
	 * ========================================= 私有方法 end
	 * ===============================================
	 */
}
